using System;
using System.Collections.Generic;
using System.Linq;
using Nop.Core;
using Nop.Core.Data;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Stores;
using Nop.Services.Events;
using Nop.Services.Logistics;
using Nop.Services.Stores;

namespace Nop.Services.Orders
{
    public class InvoiceService : IInvoiceService
    {
        private readonly IRepository<Order> _orderRepository;
        private readonly IRepository<Invoice> _invoiceRepository;
        private readonly InvoiceSettings _invoiceSettings;
        private readonly LogisticsInformationSettings _logisticsInformationSettings;
        private readonly ILogisticsService _logisticsService;
        private readonly IStoreService _storeService;
        private readonly IEventPublisher _eventPublisher;

        public InvoiceService(
            IRepository<Order> orderRepository,
            IRepository<Invoice> invoiceRepository,
            LogisticsInformationSettings logisticsInformationSettings,
            ILogisticsService logisticsService,
            InvoiceSettings invoiceSettings,
            IStoreService storeService,
            IEventPublisher eventPublisher)
        {
            _invoiceRepository = invoiceRepository;
            _orderRepository = orderRepository;
            _logisticsInformationSettings = logisticsInformationSettings;
            _logisticsService = logisticsService;
            _invoiceSettings = invoiceSettings;
            _storeService = storeService;
            _eventPublisher = eventPublisher;
        }

        protected Store GetInvoiceStore(IEnumerable<Order> orders)
        {
            var o = orders.FirstOrDefault();
            if (o == null)
            {
                throw new NopException("Invoice doesn't conatin any orders");
            }

            if (!o.StoreId.HasValue)
            {
                throw new NopException("Invoice doesn't has store id");
            }

            var store = _storeService.GetStoreById(o.StoreId.Value);
            if (store == null)
            {
                throw new NopException("Invoice doesn't has store id");
            }

            if (!orders.All(x => x.StoreId == o.StoreId))
            {
                throw new NopException("Invoice contains orders from different stores.");
            }

            return store;
        }

        protected OrderKind GetInvoiceOrderKind(IEnumerable<Order> orders)
        {
            var o = orders.FirstOrDefault();
            if (o == null)
            {
                throw new NopException("Invoice doesn't conatin any orders");
            }

            if (!orders.All(x => x.OrderKind == o.OrderKind))
            {
                throw new NopException("Invoice contains different kinds of orders.");
            }

            return o.OrderKind;
        }

        public virtual District GetInvoiceDistrict(Invoice invoice)
        {
            int districtId = invoice.DistrictId;

            var o = invoice.Orders.FirstOrDefault();
            if (o == null)
            {
                throw new NopException("Invoice doesnt conatin any orders");
            }

            if (districtId == 0)
            {
                districtId = o.DistrictId;
            }

            var district = _logisticsService.GetDistrictById(districtId);

            if (district == null)
            {
                throw new NopException("Order doesnt have the correct district ID {0}. order ID {1}", o.DistrictId, o.Id);
            }

            if (!invoice.Orders.All(x => x.DistrictId == o.DistrictId))
            {
                var orders = String.Join(",", invoice.Orders.Select(x => String.Format("Order {0}->District {1}", x.Id, x.DistrictId)).ToArray());
                throw new NopException("Invoice contains orders from different districts. ID: {0}, Orders: {1}", invoice.Id, orders);
            }

            return district;
        }

        public virtual Invoice GetInvoiceById(int invoiceId)
        {
            return _invoiceRepository.GetById(invoiceId);
        }

        public virtual IList<Invoice> GetInvoicesByIds(int[] invoiceIds)
        {
            if (invoiceIds == null || invoiceIds.Length == 0)
                return new List<Invoice>();

            var query = from i in _invoiceRepository.Table
                        where invoiceIds.Contains(i.Id)
                        select i;
            return query.ToList();
        }

        public virtual IList<Invoice> GetInvoicesByCustomerId(int customerId, bool showAllStatus = false)
        {
            var query = from i in _invoiceRepository.Table
                        orderby i.CreatedOnUtc descending
                        where i.CustomerId == customerId
                        select i;
            if (!showAllStatus)
            {
                query = query.Where(i =>
                    i.InvoiceStatusId == (int)InvoiceStatus.Published ||
                    i.InvoiceStatusId == (int)InvoiceStatus.Paid ||
                    i.InvoiceStatusId == (int)InvoiceStatus.PaidByCreditCard ||
                    i.InvoiceStatusId == (int)InvoiceStatus.PaidByCheck ||
                    i.InvoiceStatusId == (int)InvoiceStatus.Refunded ||
                    i.InvoiceStatusId == (int)InvoiceStatus.PartiallyRefunded
                );
            }

            return query.ToList();
        }

        public virtual Invoice GetInvoiceByGuid(Guid invoiceGuid)
        {
            var query = from i in _invoiceRepository.Table
                        orderby i.CreatedOnUtc descending
                        where i.InvoiceGuid == invoiceGuid
                        select i;

            return query.SingleOrDefault();
        }

        public virtual Invoice GetInvoiceByNumber(string invoiceNumber)
        {
            var query = from i in _invoiceRepository.Table
                        orderby i.CreatedOnUtc descending
                        where i.InvoiceNumber == invoiceNumber
                        select i;

            return query.SingleOrDefault();
        }

        public virtual IPagedList<Invoice> SearchInvoices(int? customerId, DateTime? startTime, DateTime? endTime, InvoiceStatus? status, string email, string billingEmail, string invoiceGuid, string invoiceNumber, string name, OrderKind? orderKind, int? districtId, int storeId, int pageIndex, int pageSize)
        {
            int? invoiceStatusId = null;
            if (status.HasValue)
                invoiceStatusId = (int)status.Value;

            var query = _invoiceRepository.Table;

            if (customerId.HasValue)
                query = query.Where(i => customerId == i.CustomerId);
            if (startTime.HasValue)
                query = query.Where(i => startTime.Value <= i.CreatedOnUtc);
            if (endTime.HasValue)
                query = query.Where(i => endTime.Value >= i.CreatedOnUtc);
            if (status.HasValue)
                query = query.Where(i => invoiceStatusId == i.InvoiceStatusId);
            if (districtId.HasValue)
                query = query.Where(i => districtId.Value == i.DistrictId);
            if (orderKind.HasValue)
                query = query.Where(i => (int)orderKind.Value == i.OrderKindId);
            if (!String.IsNullOrEmpty(email))
                query = query.Where(i => i.Customer != null && !String.IsNullOrEmpty(i.Customer.Email) && i.Customer.Email.Contains(email));
            if (!String.IsNullOrEmpty(billingEmail))
                query = query.Where(i => i.Customer != null && !String.IsNullOrEmpty(i.Customer.BillingAddress.Email) && i.Customer.BillingAddress.Email.Contains(billingEmail));
            if (!String.IsNullOrEmpty(name))
                query = query.Where(i => !String.IsNullOrEmpty(i.Name) && i.Name.Contains(name));
            if (!String.IsNullOrEmpty(invoiceNumber))
                query = query.Where(i => i.InvoiceNumber.Contains(invoiceNumber));
            if (storeId > Store.AllStoreReservedId)
                query = query.Where(i => i.StoreId == storeId);
            query = query.OrderByDescending(i => i.CreatedOnUtc).ThenByDescending(i => i.Id);

            //filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string
            if (!String.IsNullOrEmpty(invoiceGuid))
            {
                var invoices = query.ToList();
                invoices = invoices.FindAll(i => i.InvoiceGuid.ToString().ToLowerInvariant().Contains(invoiceGuid.ToLowerInvariant()));
                return new PagedList<Invoice>(invoices, pageIndex, pageSize);
            }
            else
            {
                //Database layer paging
                return new PagedList<Invoice>(query, pageIndex, pageSize);
            }
        }

        public virtual decimal GetTotalDueByCustomer(Customer customer)
        {
            if (customer == null)
                new ArgumentNullException("customer");

            var query = from i in _invoiceRepository.Table
                        orderby i.CreatedOnUtc descending
                        where i.CustomerId == customer.Id && i.InvoiceStatusId == (int)InvoiceStatus.Published
                        select i;

            return query.Sum(i => i.Total);
        }

        /// <summary>
        /// Get invoice report
        /// </summary>
        /// <param name="startTime">Invoice start time; null to load all invoices</param>
        /// <param name="endTime">Invoice end time; null to load all invoices</param>
        /// <param name="status">Invoice status; null to load all invoices</param>
        /// <param name="email">Customer Email</param>
        /// <param name="billingEmail">Invoice billing emaili; null to load all invoices</param>
        /// <param name="invoiceGuid">Invoice identifier; null to load all invoices</param>
        /// <param name="invoiceNumber">Invoice number; null to load all invoices</param>
        /// <param name="name">The invoice name; null to load all invoices</param>
        /// <param name="orderKind">The order kind; null to load all invoices</param>
        /// <param name="districtId">The district identifier; null to load all invoices</param>
        /// <param name="storeId">Store identifier</param>
        /// <returns>Invoice report</returns>
        public virtual InvoiceReport GetInvoiceReport(DateTime? startTime, DateTime? endTime, InvoiceStatus? status, string email, string billingEmail, string invoiceGuid, string invoiceNumber, string name, OrderKind? orderKind, int? districtId, int storeId)
        {
            int? invoiceStatusId = null;
            if (status.HasValue)
                invoiceStatusId = (int)status.Value;

            var query = _invoiceRepository.Table;

            if (startTime.HasValue)
                query = query.Where(i => startTime.Value <= i.CreatedOnUtc);
            if (endTime.HasValue)
                query = query.Where(i => endTime.Value >= i.CreatedOnUtc);
            if (status.HasValue)
                query = query.Where(i => invoiceStatusId == i.InvoiceStatusId);
            if (districtId.HasValue)
                query = query.Where(i => districtId.Value == i.DistrictId);
            if (orderKind.HasValue)
                query = query.Where(i => (int)orderKind.Value == i.OrderKindId);
            if (!String.IsNullOrEmpty(email))
                query = query.Where(i => i.Customer != null && !String.IsNullOrEmpty(i.Customer.Email) && i.Customer.Email.Contains(email));
            if (!String.IsNullOrEmpty(billingEmail))
                query = query.Where(i => i.Customer != null && !String.IsNullOrEmpty(i.Customer.BillingAddress.Email) && i.Customer.BillingAddress.Email.Contains(billingEmail));
            if (!String.IsNullOrEmpty(name))
                query = query.Where(i => !String.IsNullOrEmpty(i.Name) && i.Name.Contains(name));
            if (!String.IsNullOrEmpty(invoiceNumber))
                query = query.Where(i => i.InvoiceNumber.Contains(invoiceNumber));
            if (storeId > Store.AllStoreReservedId)
                query = query.Where(i => i.StoreId == storeId);
            var report = (from i in query
                          group i by 1 into result
                          select new { CountInvoices = result.Count(), SumInvoices = result.Sum(x => x.Total), SumTax = result.Sum(x => x.Tax) }
                ).Select(r => new InvoiceReport() { SumTax = r.SumTax, CountInvoices = r.CountInvoices, SumInvoices = r.SumInvoices }).FirstOrDefault();
            report = report ?? new InvoiceReport() { CountInvoices = 0, SumInvoices = decimal.Zero, SumTax = decimal.Zero };
            return report;
        }

        public virtual void DeleteInvoice(Invoice invoice)
        {
            if (invoice == null)
                throw new ArgumentNullException("invoice");

            invoice.InvoiceStatus = InvoiceStatus.Deleted;
            invoice.Orders.Clear();

            UpdateInvoice(invoice);
        }

        public virtual void InsertInvoice(Invoice invoice)
        {
            if (invoice == null)
                throw new ArgumentNullException("invoice");

            if (!Enum.IsDefined(typeof(OrderKind), invoice.OrderKindId))
                throw new ArgumentNullException("invoice.OrderKindId");

            invoice.InvoiceGuid = Guid.NewGuid();

            var district = GetInvoiceDistrict(invoice);

            invoice.DistrictId = (int)district.Id;
			_invoiceRepository.UseMasterDB();
            var count = (from i in _invoiceRepository.Table
                         where i.OrderKindId == invoice.OrderKindId && i.DistrictId == invoice.DistrictId
                         select i)
                         .Count();

            invoice.InvoiceNumber = String.Format(_invoiceSettings.NumberFormat ?? "{0}{1}{2}", ((OrderKind)invoice.OrderKindId).ToCode(), district.Code, count + 1);
            _invoiceRepository.Insert(invoice);

            //event notification
            _eventPublisher.EntityInserted(invoice);
        }

        public virtual void UpdateInvoice(Invoice invoice)
        {
            if (invoice == null)
                throw new ArgumentNullException("invoice");

            _invoiceRepository.Update(invoice);

            //event notification
            _eventPublisher.EntityUpdated(invoice);
        }

        public virtual InvoiceTotal GetTotalCharge(IEnumerable<Order> orders, int? orderStatus = null, int? shippingStatus = null, int? paymentStatus = null)
        {
            var query = orders ?? _orderRepository.Table.Where(o=>!o.Deleted&&o.OrderTotal!=0);
            if (orderStatus.HasValue)
                query = query.Where(o => o.OrderStatusId == orderStatus.Value);
            if (shippingStatus.HasValue)
                query = query.Where(o => o.ShippingStatusId == shippingStatus.Value);
            if (paymentStatus.HasValue)
                query = query.Where(o => o.PaymentStatusId == paymentStatus.Value);

            var orderKind = GetInvoiceOrderKind(orders);

            var invoiceTotal = new InvoiceTotal()
            {
                Count = 0,
                FuelSurchargeTotal = null,
                MonthlyChargeTotal = null,
                ServiceChargeTotal = null,
                StorageChargeTotal = null,
                TransactionProcessingChargeTotal = null,
                SubTotal = decimal.Zero,
                TaxTotal = decimal.Zero,
                Total = decimal.Zero,
                OrderKind = orderKind
            };

            if (orderKind == OrderKind.Express)
            {
                invoiceTotal.Count = query.Count();
                invoiceTotal.SubTotal = query.Sum(o => o.OrderSubtotalExclTax);
                invoiceTotal.FuelSurchargeTotal = query.Sum(o => o.OrderShippingExclTax);
                invoiceTotal.TaxTotal = query.Sum(o => o.OrderTax);
                invoiceTotal.Total = query.Sum(o => o.OrderTotal);
            }
            else if (orderKind == OrderKind.Commerce)
            {
                //TODO: Fix refund orders
                var store = GetInvoiceStore(orders);
                invoiceTotal.Count = query.Count();
                decimal discount = query.Sum(o => o.OrderSubTotalDiscountExclTax);
                decimal orderSubtotalExclTax = query.Sum(o => o.OrderSubtotalExclTax);
                decimal orderTotalWithDiscount = orderSubtotalExclTax - discount;
                invoiceTotal.ServiceChargeTotal = store.ServiceChargeRate / 100 * orderTotalWithDiscount;
                invoiceTotal.StorageChargeTotal =
                    (store.StorageChargeRate.HasValue ? store.StorageChargeRate.Value : _invoiceSettings.StorageChargeRate) / 100 * store.StorageAmount;
                invoiceTotal.MonthlyChargeTotal = store.MonthlyCharge;
                invoiceTotal.TransactionProcessingChargeTotal = 0M;
                var refundOrders = query.Where(o => o.PaymentStatusId == (int)PaymentStatus.Refunded || o.PaymentStatusId == (int)PaymentStatus.PartiallyRefunded).ToList();
                if (refundOrders != null && refundOrders.Count > 0)
                {
                    var refundChargeTotal = refundOrders.Sum(o => o.RefundedAmount);
                    if (store.TransactionProcessingChargeRate == 0 || !store.TransactionProcessingChargeRate.HasValue)
                        store.TransactionProcessingChargeRate = _invoiceSettings.TransactionProcessingChargeRate;//Default is 5%
                    invoiceTotal.TransactionProcessingChargeTotal = refundChargeTotal * store.TransactionProcessingChargeRate / 100;
                }
                //invoiceTotal.SubTotal =
                //    invoiceTotal.ServiceChargeTotal.Value + invoiceTotal.StorageChargeTotal.Value
                //    + invoiceTotal.MonthlyChargeTotal.Value + invoiceTotal.TransactionProcessingChargeTotal.Value;
                invoiceTotal.SubTotal = query.Sum(d => d.OrderSubtotalExclTax);
                //TODO: should get tax from service
                //invoiceTotal.TaxTotal = invoiceTotal.SubTotal * 0.05M;
                invoiceTotal.TaxTotal = query.Sum(d => d.OrderTax);
                //invoiceTotal.Total = invoiceTotal.SubTotal + invoiceTotal.TaxTotal;
                invoiceTotal.Total = query.Sum(d => d.OrderTotal);
            }
            else
            {
                throw new NotSupportedException();
            }

            return invoiceTotal;
        }

        /// <summary>
        /// Create an Invoice given an order
        /// </summary>
        /// <param name="orderId">Order ID</param>
        /// <param name="customer">Customer</param>
        /// <param name="invoiceStatus">Invoice Status</param>
        /// <returns>Invoice</returns>
        public virtual Invoice CreateInvoice(int orderId, Customer customer, InvoiceStatus invoiceStatus)
        {
            if (orderId <= 0)
                throw new ArgumentException("Invalid orderId.");

            if (customer == null)
                throw new ArgumentNullException("customer");

            var order = _orderRepository.GetById(orderId);
            if (order == null)
                throw new Exception(String.Format("Order not found for order ID: {0}", orderId));

            var orders = new List<Order> { order };
            var charge = GetTotalCharge(orders);

            var utcNow = DateTime.UtcNow;

            var invoice = new Invoice()
            {
                Name = String.IsNullOrWhiteSpace(_invoiceSettings.LogisticsPayPerUse) ?
                    order.CreatedOnUtc.ToString("d", System.Globalization.CultureInfo.InvariantCulture) :
                    String.Format("{0} {1}", _invoiceSettings.LogisticsPayPerUse, order.CreatedOnUtc.ToString("d", System.Globalization.CultureInfo.InvariantCulture)),
                CustomerId = customer.Id,
                //InvoiceGuid (auto-added during DB record insert)
                //InvoiceNumber (auto-added during DB record insert)
                InvoiceStatus = invoiceStatus,
                Comment = null,  // default null
                SubTotal = charge.SubTotal,
                FuelSurcharge = charge.FuelSurchargeTotal,
                Tax = charge.TaxTotal,
                Total = charge.Total,
                OrderChargePercentage = 1M, // default 100%
                Discount = 0M, // default 0
                CreatedOnUtc = utcNow,
                DueDate = utcNow,
                Orders = orders,
            };

            District district = null;

            if (charge.OrderKind == OrderKind.Commerce)
            {
                district = _logisticsService.GetAllDistricts().First();
            }
            else
            {
                district = GetInvoiceDistrict(invoice);
            }

            invoice.DistrictId = district.Id;
            invoice.TimeZoneId = district.TimeZoneId;
            invoice.OrderKindId = (int)order.OrderKindId;
            InsertInvoice(invoice);

            return invoice;
        }

        /// <summary>
        /// Create an Invoice given an order
        /// </summary>
        /// <param name="orderId">order Id</param>
        /// <param name="customer">custer</param>
        /// <param name="invoiceStatus">Invoice Status</param>
        /// <returns>Invoice</returns>
        public virtual Invoice CreateSingleInvoice(int orderId, Customer customer, InvoiceStatus invoiceStatus)
        {
            if (orderId <= 0)
                throw new ArgumentException("Invalid orderId.");
            if (customer == null)
                throw new ArgumentNullException("customer");

            var order = _orderRepository.GetById(orderId);
            if (order == null)
                throw new Exception(String.Format("Order not found for order ID: {0}", orderId));

            var utcNow = DateTime.UtcNow;
            var district = _logisticsService.GetAllDistricts().First();
            var invoice = new Invoice()
            {
                Name = String.IsNullOrWhiteSpace(_invoiceSettings.LogisticsPayPerUse) ?
                    order.CreatedOnUtc.ToString("d", System.Globalization.CultureInfo.InvariantCulture) :
                    String.Format("{0} {1}", _invoiceSettings.LogisticsPayPerUse, order.CreatedOnUtc.ToString("d", System.Globalization.CultureInfo.InvariantCulture)),
                CustomerId = customer.Id,
                InvoiceStatus = invoiceStatus,
                Comment = null,  // default null
                SubTotal = order.OrderSubtotalInclTax,
                FuelSurcharge = 0,
                Tax = order.OrderSubtotalInclTax - order.OrderSubtotalExclTax,
                Total = order.OrderTotal,
                OrderChargePercentage = 1M, // default 100%
                Discount = order.OrderDiscount, // default 0
                CreatedOnUtc = utcNow,
                DueDate = utcNow,
                Orders = new Order[] { order },
                DistrictId = district.Id,
                TimeZoneId = district.TimeZoneId,
                OrderKindId = (int)order.OrderKindId,
                StoreId = order.StoreId
            };

            InsertInvoice(invoice);
            return invoice;
        }

        /// <summary>
        /// Create an Invoice given orders
        /// </summary>
        /// <param name="orderIds">Order identifiers for this invoice</param>
        /// <param name="name">Name</param>
        /// <param name="comment">Comment</param>
        /// <param name="customer">Customer</param>
        /// <param name="dueDate">Due date</param>
        /// <param name="invoiceStatus">Invoice Status</param>
        /// <returns>Invoice</returns>
        public virtual Invoice CreateInvoice(int[] orderIds, string name, string comment, Customer customer, DateTime dueDate, InvoiceStatus invoiceStatus)
        {
            if (orderIds == null || orderIds.Length <= 0)
                throw new ArgumentException("Invalid orderIds.");

            if (customer == null)
                throw new ArgumentNullException("customer");

            var orders = _orderRepository.Table.Where(o => orderIds.Contains(o.Id));
            var charge = GetTotalCharge(orders);

            var utcNow = DateTime.UtcNow;
            int? storeId = null;
            if (charge.OrderKind == OrderKind.Commerce)
            {
                if (customer.StoreId == null)
                {
                    throw new ArgumentNullException("store");
                }
                storeId = customer.StoreId;

                if (storeId == Store.AllStoreReservedId)
                    storeId = orders.FirstOrDefault().StoreId;
                if (!storeId.HasValue || orders.Any(o => o.StoreId != storeId))
                    throw new NopException("Not all orders are in the same store");
                if (orders.Any(o => o.PaymentStatusId == (int)PaymentStatus.Authorized
                                || o.PaymentStatusId == (int)PaymentStatus.Pending
                                || o.PaymentStatusId == (int)PaymentStatus.Voided))
                {
                    throw new NopException("Only Paid, Refunded and PartiallyRefunded orders can be created as invoice");
                }
            }

            var invoice = new Invoice()
            {
                Name = name,
                CustomerId = customer.Id,
                //InvoiceGuid (auto-added during DB record insert)
                //InvoiceNumber (auto-added during DB record insert)
                InvoiceStatus = invoiceStatus,
                Comment = null,  // default null
                SubTotal = charge.SubTotal,
                FuelSurcharge = charge.FuelSurchargeTotal,
                MonthlyCharge = charge.MonthlyChargeTotal,
                StorageCharge = charge.StorageChargeTotal,
                TransactionProcessingCharge = charge.TransactionProcessingChargeTotal,
                ServiceCharge = charge.ServiceChargeTotal,
                Tax = charge.TaxTotal,
                Total = charge.Total,
                OrderChargePercentage = 1M, // default 100%
                Discount = 0M, // default 0
                CreatedOnUtc = DateTime.UtcNow,
                DueDate = dueDate.Date,
                Orders = orders.ToList(),
                StoreId = storeId,
                OrderKindId = (int)charge.OrderKind,
            };

            if (charge.OrderKind == OrderKind.Commerce)
            {
                invoice.DistrictId = _logisticsService.GetAllDistricts().First().Id;
            }
            var district = GetInvoiceDistrict(invoice);
            invoice.DistrictId = district.Id;
            invoice.TimeZoneId = district.TimeZoneId;
            InsertInvoice(invoice);

            return invoice;
        }

        /// <summary>
        /// Create Invoices per customer by given conditions
        /// </summary>
        /// <param name="startTime">Invoice start time</param>
        /// <param name="endTime">Invoice end time</param>
        /// <param name="DistrictId">The identifier of the district which invoice belongs to</param>
        /// <param name="name">Invoice name</param>
        /// <param name="dueDate">Invoice due date</param>
        /// <param name="orderKind">Order kind of the invoice</param>
        public virtual void BulkCreateInvoices(DateTime startTime, DateTime endTime, int DistrictId, string name, DateTime dueDate, OrderKind orderKind)
        {
            if (orderKind != OrderKind.Express)
            {
                throw new NotImplementedException("The express orders are not supported.");
            }

            var district = _logisticsService.GetDistrictById(DistrictId);
            if (district == null)
            {
                throw new ArgumentException("The DistrictId is invalid.");
            }

            var utcNow = DateTime.UtcNow;

            var query = from o in _orderRepository.Table
                        where o.CreatedOnUtc >= startTime &&
                            o.CreatedOnUtc <= endTime &&
                            o.InvoiceId == null &&
                            o.OrderKindId == (int?)orderKind &&
                            o.DistrictId == DistrictId
                        group o by o.CustomerId into g
                        orderby g.Key
                        select new
                        {
                            CustomerId = g.Key,
                            SubTotal = g.Sum(x => x.OrderSubtotalExclTax),
                            FuelSurcharge = g.Sum(x => x.OrderShippingExclTax),
                            Tax = g.Sum(x => x.OrderTax),
                            Total = g.Sum(x => x.OrderTotal),
                            Orders = g
                        };

            query.Where(x => x.Orders.Count() > 0 && x.Total > 0)
                .ToList()
                .ForEach(x => InsertInvoice(new Invoice()
                    {
                        Name = name,
                        CustomerId = x.CustomerId,
                        //InvoiceGuid (auto-added during DB record insert)
                        //InvoiceNumber (auto-added during DB record insert)
                        InvoiceStatus = InvoiceStatus.Pending,
                        Comment = null,  // default null
                        SubTotal = x.SubTotal,
                        FuelSurcharge = x.FuelSurcharge,
                        OrderKindId = (int)orderKind,
                        Tax = x.Tax,
                        Total = x.Total,
                        OrderChargePercentage = 1M, // default 100%
                        Discount = 0M, // default 0
                        CreatedOnUtc = utcNow,
                        DueDate = dueDate.Date,
                        TimeZoneId = district.TimeZoneId,
                        Orders = x.Orders.ToList(),
                        DistrictId = district.Id
                    }));
        }

        public virtual void UpdateInvoiceStatus(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            //TODO: we need to handle the generic refunded scenario. Currently we just handle single order
            if (order.OrderKind == OrderKind.Express &&
                order.PaymentStatus == Core.Domain.Payments.PaymentStatus.Refunded &&
                order.Invoice != null &&
                order.RefundedAmount == order.OrderTotal)
            {
                var invoice = order.Invoice;
                invoice.FuelSurcharge = 0m;
                invoice.Tax = 0m;
                invoice.Discount = 0m;
                invoice.SubTotal = 0m;
                invoice.Total = 0m;
                invoice.InvoiceStatus = InvoiceStatus.Refunded;
                UpdateInvoice(invoice);

                order.OrderNotes.Add(new OrderNote()
                {
                    Note = String.Format("Invoice {0} ({1}) has updated as refunded.", invoice.InvoiceNumber, invoice.Id),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
                _orderRepository.Update(order);
            }
        }
    }
}
